import WebGl from '@/modules/three';
import {
  MeshPhysicalNodeMaterial,
  normalWorld,
  timerLocal,
  mx_noise_vec3,
  mx_worley_noise_vec3,
  mx_cell_noise_float,
  mx_fractal_noise_vec3,
} from 'three/nodes'; // 'three/nodes': 'three/examples/jsm/nodes/Nodes.js',
import assets from '@/modules/assets';
import { nodeFrame } from 'threeWebglNodes';
// import { nodeFrame } from 'three/addons/renderers/webgl-legacy/nodes/WebGLNodes.js';
// 这个要配置一下 alias别名； WebGLNodes.js这里面引用问题
import { createNoise2D } from '@/modules/local';
const noise2D = createNoise2D();

const { Three, TWEEN, dat, Stats, OrbitControls } = WebGl;

class WebGlControl {
  private container: any;
  private camera: any;
  private scene: any;
  private renderer: any;
  private stats: any;
  private animateFrame: any;
  private tween: any;
  private controls: any;
  private clock: any = new Three.Clock();
  private textureLoader = new Three.TextureLoader();
  private noiseWaterTex: any;

  private Geometrys: {
    planeGeometry?: any;
    planeGeometry2?: any;
    planeGeometry3?: any;
    cylGeometry?: any;
  } = {}; //保存所有Geometry，方便统一销毁
  private Materials: {
    planeMaterial?: any;
    planeMaterial2?: any;
    planeMaterial3?: any;
    cylMaterial?: any;
  } = {}; //保存所有Material，方便统一销毁
  private Meshs: {
    cylinder?: any;
  } = {};

  private GuiInit: any = null;
  private guiData: any = {
    // x: 0.005,
    // color: '#000000',
    // autoRotate: true,
    speedX: 0.5,
    speedY: 0.5,
    scale: 0.06,
    strength: 2,
  };

  public init = (id: string) => {
    // if(this.scene) {return}
    this.container = document.getElementById(id);
    let width = this.container.clientWidth,
      height = this.container.clientHeight;

    this.scene = new Three.Scene();

    this.camera = new Three.PerspectiveCamera(45, width / height, 1, 500);
    this.camera.position.x = 0;
    this.camera.position.y = 50;
    this.camera.position.z = 100;
    this.camera.lookAt(new Three.Vector3(10, -50, 0));

    this.renderer = new Three.WebGLRenderer();
    // this.renderer.setClearColor( 0xffffff );
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(width, height);
    this.container.appendChild(this.renderer.domElement);

    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls.enableDamping = true; //阻尼
    this.controls.dampingFactor = 0.1; //阻尼

    this.addThings();
    this.addLight(); //+光源
    this.animate(); //开始动
    this.initStats();

    this.initGui();
  };
  public destroy = () => {
    this.toggleGui(0);
    Object.values(this.Geometrys).forEach((e) => {
      e.dispose();
    });
    Object.values(this.Materials).forEach((e) => {
      e.dispose();
    });
    this.scene.clear();
    this.renderer.dispose();
    this.renderer.forceContextLoss();
    this.camera = null;
    this.scene = null;
    this.renderer = null;
    this.GuiInit = null;
    this.stats = null;
    if (this.tween) {
      this.tween.stop();
      this.tween = null;
    }
    cancelAnimationFrame(this.animateFrame);
  };

  private initStats = () => {
    this.stats = new Stats();
    this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
    this.stats.dom.style.position = 'absolute';
    this.container.appendChild(this.stats.dom);
  };

  public initGui = () => {
    if (this.GuiInit) {
      this.toggleGui();
      return;
    }
    this.GuiInit = new dat.GUI({
      // autoPlace: false
    });

    this.GuiInit.domElement.style.position = 'absolute';
    this.GuiInit.domElement.style.right = '10px';
    this.GuiInit.domElement.style.top = '10px';

    let GuiController = this.GuiInit.addFolder('控制窗口');
    // GuiController.add(this.guiData, 'x', -0.1, 0.1, 0.005).name('自转速度');
    // GuiController.addColor(this.guiData, 'color').name('背景颜色');
    GuiController.add(this.guiData, 'speedX', 0, 2, 0.005);
    GuiController.add(this.guiData, 'speedY', 0, 2, 0.005);
    GuiController.add(this.guiData, 'scale', 0.01, 0.4, 0.002);
    GuiController.add(this.guiData, 'strength', -4, 8, 0.01);

    GuiController.open();

    // let cubeGui = this.GuiInit.addFolder("cube position");
    // cubeGui.open()
  };

  public toggleGui = (state: any = null) => {
    if (!this.GuiInit || !this.GuiInit.domElement) {
      return;
    }
    let display = this.GuiInit.domElement.style.display == 'none' ? '' : 'none';
    if (state == 0) {
      display = 'none';
    }
    this.GuiInit.domElement.style.display = display;
  };

  private addLight = () => {
    let ambi = new Three.AmbientLight(0xffffff);
    ambi.intensity = 0.5;
    this.scene.add(ambi);
    let dirLight = new Three.DirectionalLight(0xffffff);
    dirLight.position.set(10, 2, 5);
    dirLight.intensity = 1.8;
    dirLight.castShadow = true;
    this.scene.add(dirLight);
    // let light = new Three.PointLight( 0xFFFF00 );
    // light.position.set( -100, 100, 0 );
    // this.scene.add(light);
  };

  private addThings = () => {
    // const axesHelper = new Three.AxesHelper( 800 );
    // this.scene.add( axesHelper )  //添加坐标轴；红线是X轴，绿线是Y轴，蓝线是Z轴

    this.addNoiseBalls();
    this.addPlane();
    this.addGlslPlane();
    this.addTexturePlane();
    this.addWavePlane();
    this.addCylinder();

    this.initTween();
  };

  private addNoiseBalls = () => {
    const geometry = new Three.SphereGeometry(8, 64, 32);

    const offsetNode = timerLocal();
    const customUV = normalWorld.mul(10).add(offsetNode);

    // left top
    let material = new MeshPhysicalNodeMaterial();
    material.colorNode = mx_noise_vec3(customUV);
    // material.normalNode = mx_noise_vec3( customUV );
    // material.opacityNode = mx_noise_vec3( customUV );
    // console.log('mx_noise_vec3(customUV)', mx_noise_vec3(customUV))
    let mesh = new Three.Mesh(geometry, material);
    mesh.position.x = -10;
    mesh.position.y = 10;
    this.scene.add(mesh);

    // right top

    material = new MeshPhysicalNodeMaterial();
    material.colorNode = mx_cell_noise_float(customUV);
    // material.normalNode = mx_cell_noise_float( customUV );
    // material.opacityNode = mx_cell_noise_float( customUV );
    mesh = new Three.Mesh(geometry, material);
    mesh.position.x = 10;
    mesh.position.y = 10;
    this.scene.add(mesh);

    // left bottom

    material = new MeshPhysicalNodeMaterial();
    material.colorNode = mx_worley_noise_vec3(customUV);

    mesh = new Three.Mesh(geometry, material);
    mesh.position.x = -10;
    mesh.position.y = -10;
    this.scene.add(mesh);

    // right bottom

    material = new MeshPhysicalNodeMaterial();
    material.colorNode = mx_fractal_noise_vec3(customUV.mul(0.2));

    mesh = new Three.Mesh(geometry, material);
    mesh.position.x = 10;
    mesh.position.y = -10;
    this.scene.add(mesh);
  };

  private addPlane = () => {
    // this.Geometrys.planeGeometry = new Three.BufferGeometry();
    this.Geometrys.planeGeometry = new Three.PlaneGeometry(60, 60, 100, 100);
    //100宽 有 101个点！！！

    let pos = this.Geometrys.planeGeometry.attributes.position.array;
    for (let i = 0; i < pos.length; i += 3) {
      // pos[i+2] = Math.random() * 2   //随机

      // pos[i+2] = Math.floor((i % 303)/3)

      let tx = Math.floor(i / 303) * this.guiData.scale + this.posBaseX;
      let ty = Math.floor((i % 303) / 3) * this.guiData.scale + this.posBaseY;
      const z = noise2D(tx, ty); // -1 ~ 1
      pos[i + 2] = z * this.guiData.strength;
    }
    this.Geometrys.planeGeometry.attributes.position.needsUpdate = true;
    // this.Geometrys.planeGeometry.computeVertexNormals();  //重新计算法线向量
    // this.Geometrys.planeGeometry.setAttribute( 'position', new Three.BufferAttribute( vertices, 3 ) );
    this.Materials.planeMaterial = new Three.MeshPhongMaterial({
      color: 0xffffff,
      side: Three.DoubleSide,
      wireframe: true,
    });
    const mesh = new Three.Mesh(
      this.Geometrys.planeGeometry,
      this.Materials.planeMaterial,
    );
    mesh.receiveShadow = true;
    mesh.castShadow = true;

    mesh.position.set(0, 18, 0);
    mesh.rotation.set(1.55, 0, 0);
    this.scene.add(mesh);
  };

  private addGlslPlane = () => {
    this.Geometrys.planeGeometry2 = new Three.PlaneGeometry(60, 60, 100, 100);
    //100宽 有 101个点！！！

    let pos = this.Geometrys.planeGeometry2.attributes.position.array;
    for (let i = 0; i < pos.length; i += 3) {
      let tx = Math.floor(i / 303) * this.guiData.scale + this.posBaseX;
      let ty = Math.floor((i % 303) / 3) * this.guiData.scale + this.posBaseY;
      const z = noise2D(tx, ty); // -1 ~ 1
      pos[i + 2] = z * this.guiData.strength;
    }
    this.Geometrys.planeGeometry2.attributes.position.needsUpdate = true;

    var uniforms = {
      color: { value: new Three.Color('#267eee') },
      val: { value: 1.0 },
      zMax: { value: this.guiData.strength },
    };
    this.Materials.planeMaterial2 = new Three.ShaderMaterial({
      uniforms: uniforms,
      vertexShader: `
        attribute float size;
        attribute vec3 position2;
        uniform float val;
        varying vec3 vPos;
        varying vec3 vNormal;

        void main() {
          vNormal = normal;
          vPos.x = position.x * val + position2.x * (1.-val);
          vPos.y = position.y* val + position2.y * (1.-val);
          vPos.z = position.z* val + position2.z * (1.-val);

          vec4 mvPosition = modelViewMatrix * vec4( vPos, 1.0 );

          // gl_PointSize = size * ( 300.0 / -mvPosition.z );

          gl_Position = projectionMatrix * mvPosition;

        }
      `,
      fragmentShader: `
        uniform vec3 color;
        uniform float zMax;

        varying vec3 vPos;
        varying vec3 vNormal;

        void main() {
          // 发光的强度
          float glowIntensity = 1.5 - vPos.z / zMax;
          
          // vec3 vColor = vec3(1.0, 0., 0.);
          // vColor.r = vPos.z/zMax;
          // vColor.g = vPos.y/50.;
          // vColor.b = vPos.x/50.;

          gl_FragColor = vec4( color.rgb * glowIntensity, 1.0 );

        }
      `,

      // blending: Three.AdditiveBlending,
      // depthTest: false,   //忽略深度测试
      // transparent: true,
      side: Three.DoubleSide,
    });

    const mesh = new Three.Mesh(
      this.Geometrys.planeGeometry2,
      this.Materials.planeMaterial2,
    );
    // mesh.receiveShadow = true;
    //           mesh.castShadow = true;

    mesh.position.set(0, -0.18, 0);
    mesh.rotation.set(1.55, 0, 0);
    this.scene.add(mesh);
  };

  private addTexturePlane = () => {
    this.noiseWaterTex = this.textureLoader.load(assets.waterNoise);

    this.noiseWaterTex.wrapS = Three.RepeatWrapping;
    this.noiseWaterTex.wrapT = Three.RepeatWrapping;
    this.noiseWaterTex.repeat.set(2, 2);

    this.Geometrys.planeGeometry3 = new Three.PlaneGeometry(60, 60, 100, 100);
    this.Materials.planeMaterial3 = new Three.MeshStandardMaterial({
      color: 0xffffff,
      // roughness: this.guiData.roughness,
      // roughnessMap: this.noiseWaterTex,
      // metalness: this.guiData.metalness,
      // metalnessMap: metalnessMap,

      map: this.noiseWaterTex,
      // normalMap: this.noiseWaterTex,
      // normalScale: new Three.Vector2( 1, - 1 ), // why does the normal map require negation in this case?

      // aoMap: aoMap,
      // aoMapIntensity: 1,

      displacementMap: this.noiseWaterTex,
      displacementScale: this.guiData.strength * 2,
      displacementBias: -0.428408,

      // envMap: reflectionCube,
      // envMapIntensity: this.guiData.envMapIntensity,

      side: Three.DoubleSide,
    });
    // this.Geometrys.planeGeometry3.computeVertexNormals();  //重新计算法线向量

    const mesh = new Three.Mesh(
      this.Geometrys.planeGeometry3,
      this.Materials.planeMaterial3,
    );
    // mesh.receiveShadow = true;
    //           mesh.castShadow = true;

    mesh.position.set(0, -14, 0);
    mesh.rotation.set(1.55, 0, 0);
    this.scene.add(mesh);
  };

  private addWavePlane = () => {
    const terrainWidthExtents = 100;
    const terrainDepthExtents = 100;
    const terrainWidth = 16;
    const terrainDepth = 16;
    const geometry = new Three.PlaneGeometry(
      terrainWidthExtents,
      terrainDepthExtents,
      terrainWidth - 1,
      terrainDepth - 1,
    );
    geometry.rotateX(-Math.PI / 2);

    const vertices = geometry.attributes.position.array;

    for (let i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
      // j + 1 because it is the y component that we modify
      // vertices[ j + 1 ] = heightData[ i ];
      vertices[j + 1] = Math.random() * 3;
    }

    geometry.computeVertexNormals(); //重新计算法线向量

    const groundMaterial = new Three.MeshPhongMaterial({
      color: 0xc7c7c7,
      flatShading: true, //平面，没有顶点过度！！！！！
    });
    const terrainMesh = new Three.Mesh(geometry, groundMaterial);
    terrainMesh.receiveShadow = true;
    terrainMesh.castShadow = true;
    terrainMesh.position.set(0, -27, 0);
    this.scene.add(terrainMesh);
  };

  private waves: any[] = [];
  private addCylinder = () => {
    this.Geometrys.cylGeometry = new Three.CylinderGeometry(
      30,
      30,
      80,
      40,
      10,
      true, //openEnded, 去掉封顶
      0,
      Math.PI * 2.1,
    );
    this.Geometrys.cylGeometry.applyMatrix4(
      new Three.Matrix4().makeRotationX(-Math.PI / 2),
    );

    // important: by merging vertices we ensure the continuity of the waves
    // this.Geometrys.cylGeometry.mergeVertices();
    // get the vertices
    var positions = this.Geometrys.cylGeometry.attributes.position.array;

    // create an array to store new data associated to each vertex
    this.waves = [];

    for (var i = 0; i < positions.length; i += 3) {
      // store some data associated to it
      this.waves.push({
        x: positions[i],
        y: positions[i + 1],
        z: positions[i + 2],
        // a random angle
        ang: Math.random() * Math.PI * 2,
        // a random distance
        amp: 2 + Math.random() * 3,
        // a random speed between 0.016 and 0.048 radians / frame
        speed: 0.016 + Math.random() * 0.032,
      });
    }
    this.Materials.cylMaterial = new Three.MeshPhongMaterial({
      color: 0x0000ff,
      transparent: true,
      opacity: 0.9,
      flatShading: true, //平面，没有顶点过度！！！！！
    });

    this.Meshs.cylinder = new Three.Mesh(
      this.Geometrys.cylGeometry,
      this.Materials.cylMaterial,
    );
    this.Meshs.cylinder.receiveShadow = true;
    this.Meshs.cylinder.castShadow = true;
    this.Meshs.cylinder.position.set(55, -5, 20);

    this.scene.add(this.Meshs.cylinder);
  };
  private cylinderMove = () => {
    // get the vertices
    var verts = this.Geometrys.cylGeometry.attributes.position.array;
    var l = verts.length;

    for (var i = 0; i < l; i += 3) {
      // var v = verts[i];

      // get the data associated to it
      var vprops = this.waves[i / 3];

      // update the position of the vertex
      verts[i] = vprops.x + Math.cos(vprops.ang) * vprops.amp;
      verts[i + 1] = vprops.y + Math.sin(vprops.ang) * vprops.amp;

      // increment the angle for the next frame
      vprops.ang += vprops.speed;
    }
    this.Geometrys.cylGeometry.computeVertexNormals(); //重新计算法线向量
    // Tell the renderer that the geometry of the sea has changed.
    // In fact, in order to maintain the best level of performance,
    // three.js caches the geometries and ignores any changes
    // unless we add this line
    this.Geometrys.cylGeometry.attributes.position.needsUpdate = true;

    this.Meshs.cylinder.rotation.z += 0.005;
  };

  private initTween = () => {
    // var pos: any = { val: 1 };
    // this.tween = new TWEEN.Tween(pos)
    //   .to({ val: 0 }, 1000)
    //   .easing(TWEEN.Easing.Quadratic.InOut)
    //   .delay(200)
    //   .onUpdate(callback);
    // let tweenBack = new TWEEN.Tween(pos)
    //   .to({ val: 1 }, 1000)
    //   .easing(TWEEN.Easing.Quadratic.InOut)
    //   .delay(200)
    //   .onUpdate(callback);
    // this.tween.chain(tweenBack);
    // tweenBack.chain(this.tween);
    // this.tween.start();
    // let that = this;
    // function callback(res) {
    //   that.Materials.starsMaterial.uniforms.val.value = res.val;
    // }
  };

  private animate = () => {
    // if(!this.renderer){return;}
    if (this.stats) {
      this.stats.begin();
    }

    this.render();
    TWEEN.update();
    this.controls.update();

    if (this.stats) {
      this.stats.end();
    }
    this.animateFrame = requestAnimationFrame(this.animate);
  };

  private posBaseX: number = 0;
  private posBaseY: number = 0;
  private render = () => {
    const delta = this.clock.getDelta();
    this.renderer.setClearColor(this.guiData.color, 1.0);
    // if(this.guiData.autoRotate){
    // }
    nodeFrame.update();

    this.cylinderMove();

    let pos = this.Geometrys.planeGeometry.attributes.position.array;
    let pos2 = this.Geometrys.planeGeometry2.attributes.position.array;
    this.posBaseX += delta * this.guiData.speedX;
    this.posBaseY += delta * this.guiData.speedY;
    for (let i = 0; i < pos.length; i += 3) {
      let tx = Math.floor(i / 303) * this.guiData.scale + this.posBaseX;
      let ty = Math.floor((i % 303) / 3) * this.guiData.scale + this.posBaseY;
      const z = noise2D(tx, ty);
      pos[i + 2] = z * this.guiData.strength;

      pos2[i + 2] = z * this.guiData.strength;
    }
    this.Geometrys.planeGeometry.attributes.position.needsUpdate = true;
    this.Geometrys.planeGeometry2.attributes.position.needsUpdate = true;
    // this.Geometrys.planeGeometry.computeVertexNormals();  //重新计算法线向量

    this.noiseWaterTex.offset.set(this.posBaseY * 0.5, this.posBaseX * -0.5);

    this.renderer.render(this.scene, this.camera);
  };
}

const webGlControl = new WebGlControl();
export default webGlControl;
